﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using System.Web.UI.HtmlControls;
using uk.ac.soton.ses.DAL;
using System.Web;
using uk.ac.soton.ses.WebControls;
using System.Security.Principal;
using uk.ac.soton.ses.UserControls;
using uk.ac.soton.ses.layouts;

namespace uk.ac.soton.ses.layouts
{
    public partial class ParameterGraphXY : LayoutsPageBase
    {
        //Parameter selection data, keyed by parameter path
        private Dictionary<string, XYParameterGraphExperimentParameterSelection> _experimentParameterSelectionList = new Dictionary<string, XYParameterGraphExperimentParameterSelection>();
        private Dictionary<string, XYParameterGraphParameterSelection> _globalParameterSelectionList = new Dictionary<string, XYParameterGraphParameterSelection>();

        //Page variables
        private bool IsInitialised = false;
        private bool redirect = false;
        MaterialsMetadataContext dbcontext = null;
        private ExperimentMultipleSelection _experimentSelectionControl = null;
        private ExperimentQuickSearch _saveReportSearchControl = null;

        //Useful properties
        private IEnumerable<XYParameterGraphExperimentParameterSelection> XSeriesParameters { get { return this._experimentParameterSelectionList.Values.Where(info => info.HasXDataItem); } }
        private IEnumerable<XYParameterGraphExperimentParameterSelection> YSeriesParameters { get { return this._experimentParameterSelectionList.Values.Where(info => info.HasYDataItem); } }
        private IEnumerable<XYParameterGraphParameterSelection> XGlobalParameters { get { return this._globalParameterSelectionList.Values.Where(info => info.HasXDataItem); } }
        private IEnumerable<XYParameterGraphParameterSelection> YGlobalParameters { get { return this._globalParameterSelectionList.Values.Where(info => info.HasYDataItem); } }

        #region Page Life Cycle

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            try
            {
                //Load a database context
                this.dbcontext = HttpContext.Current.GetMaterialsDBContext();

                //Find the experiments quick search box, cast it, and store it in a field
                this._experimentSelectionControl = this.ctl_ExperimentMultipleSelection as ExperimentMultipleSelection;

                //Find the experiments quick search box, cast it, and store it in a field
                this._saveReportSearchControl = this.ctl_SaveReportExperimentQuickSearch as ExperimentQuickSearch;

                //Set up events
                this._saveReportSearchControl.ExperimentSelected += new ExperimentSelectedEventHandler(this.SaveReportQuickSearchControl_ExperimentSelected);
                //Set up events we use
                this.ConfigureEvents();

                this.Page.AddHDCStylesheet();

                //Let OnLoad do its stuff
                this.IsInitialised = true;
            }
            catch (Exception)
            {
                HDCSharedWebFunctions.AddErrorLabel("Could not load data", this.PlaceHolder_Messages);
                this.IsInitialised = false;
            }
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            //Load parameters from experiments user has requested
            this.LoadParametersInRequestedExperiments();

            //Find out which parameters user has requested
            this.ProcessParameterQueryStrings();

            if (this.IsInitialised && !this.IsPostBack)
            {
                this.DataBindControls();
                this.CreateGraph();
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            if (this.redirect)
            {
                this.RedirectToNewUrl();
            }
        }

        public override void Dispose()
        {
            base.Dispose();
            if (this.dbcontext != null)
            {
                this.dbcontext.Connection.Close();
                this.dbcontext.Dispose();
            }
        }

        #endregion Page Life Cycle


        #region Private methods


        private void ConfigureEvents()
        {
            //Events
            this._experimentSelectionControl.ExperimentSelectionChanged += new ExperimentSelectionChangedEventHandler(this.ExperimentSelectionChanged);
        }
        
        private void LoadParametersInRequestedExperiments()
        {
            //Only load parameters if an experiment has been requested
            foreach (int expID in this._experimentSelectionControl.RequestedExperimentIDs)
            {
                //Get the first experiment and load its parameters
                IQueryable<ExperimentParameter> experimentParameters = this.dbcontext.ExperimentParameters.Include("ExperimentParameterParents.ExperimentParameterParent").Where(p => p.Experiment.ID == expID);

                foreach (ExperimentParameter expParam in experimentParameters)
                {
                    //Store the parameter for list view
                    string paramIDPath = expParam.GetParameterIDPath(this.dbcontext, false) + "\\" + expParam.Name;
                    if (!this._experimentParameterSelectionList.ContainsKey(paramIDPath))
                    {
                        this._experimentParameterSelectionList[paramIDPath] = new XYParameterGraphExperimentParameterSelection(paramIDPath)
                        {
                            DataItemName = expParam.Name,
                            ParameterIDPath = paramIDPath,
                            ParameterNamePath = expParam.GetParameterNamePath(this.dbcontext, true),
                            ExperimentID = expID
                        };

                        //Look up the parent parameter (if it exists)
                        ExperimentParameter parentParam = this.dbcontext.GetParentParameter(expParam);
                        if (parentParam != null)
                        {
                            this._experimentParameterSelectionList[paramIDPath].ParentParameterID = (int?)parentParam.ID;
                        }
                    }
                }
            }
        }

        private bool ProcessParameterQueryStrings()
        {
            //Get parameters
            string[] xseriesparameterstrings = Page.Request.QueryString.GetValues("XSeriesParameter") ?? new string[0];
            string[] yseriesparameterstrings = Page.Request.QueryString.GetValues("YSeriesParameter") ?? new string[0];
            string[] xglobalparameterstrings = Page.Request.QueryString.GetValues("XGlobalParameter") ?? new string[0];
            string[] yglobalparameterstrings = Page.Request.QueryString.GetValues("YGlobalParameter") ?? new string[0];

            //Parse IDs
            try
            {
                this.ParseSeriesParameterStrings(xseriesparameterstrings, XYGraphDataItemSelection.XSeriesSetter, this.XSeriesParameters, XYGraphDataItemSelection.XSeriesGetter);
                this.ParseSeriesParameterStrings(yseriesparameterstrings, XYGraphDataItemSelection.YSeriesSetter, this.YSeriesParameters, XYGraphDataItemSelection.YSeriesGetter);
                this.ParseGlobalParameterStrings(xglobalparameterstrings, XYGraphDataItemSelection.XSeriesSetter, this.XGlobalParameters, XYGraphDataItemSelection.XSeriesGetter);
                this.ParseGlobalParameterStrings(yglobalparameterstrings, XYGraphDataItemSelection.YSeriesSetter, this.YGlobalParameters, XYGraphDataItemSelection.YSeriesGetter);
            }
            catch (Exception ex)
            {
                if (ex is FormatException || ex is OverflowException || ex is ArgumentNullException)
                {
                    HDCSharedWebFunctions.AddErrorLabel("Could not parse ID", this.PlaceHolder_Messages);
                    return false;
                }
                else
                {
                    throw;
                }
            }

            return true;
        }

        private void ParseSeriesParameterStrings(string[] seriesparameterstrings, XYGraphDataItemSelection.XYGraphDataItemSelectionSeriesSetter seriesSetter, IEnumerable<XYParameterGraphExperimentParameterSelection> definedSeriesParameters, XYGraphDataItemSelection.XYGraphDataItemSelectionSeriesGetter seriesGetter)
        {
            foreach (string paramstring in seriesparameterstrings)
            {
                string[] splitString;

                //Check we managed to split the string into 4 parts
                if (!HDCSharedFunctions.TrySplitString(paramstring, ':', 4, out splitString))
                {
                    continue;
                }

                string seriesname = splitString[0];
                string experimentidstr = splitString[1];
                string parentparamidstr = splitString[2];
                string paramname = splitString[3];
                int experimentid;
                int parentparamid;

                //Check that the experiment id is an integer and it is greater than 0. Also check that the parent parameter is an integer (if it is 0, it means there is no parent parameter)
                if (int.TryParse(experimentidstr, out experimentid) && experimentid > 0 && int.TryParse(parentparamidstr, out parentparamid))
                {
                    string parampath;
                    Experiment experiment = this.dbcontext.GetExperimentByID(experimentid);
                    if (experiment == null)
                    {
                        continue;
                    }

                    //If parent parameter id is 0 (no parent) then search at the top level, otherwise search at the level of the parent parameter
                    if (parentparamid > 0)
                    {
                        //Retrieve parent parameter
                        ExperimentParameter parentparam = this.dbcontext.GetExperimentParameterByID(parentparamid);

                        //Verify it exists
                        if (parentparam == null)
                        {
                            continue;
                        }

                        //Look up the parameter path for a parameter with the name requested
                        IEnumerable<ExperimentParameter> childparams = parentparam.GetChildParameters(paramname);

                        //If a parameter does not exist with the name requested, don't graph anything
                        ExperimentParameter firstParam = childparams.FirstOrDefault();
                        if (firstParam == null)
                        {
                            continue;
                        }

                        //Look up the parameter path for one of the parameters
                        parampath = firstParam.GetParameterIDPath(this.dbcontext, false) + "\\" + firstParam.Name;
                    }
                    //Search at the top level
                    else
                    {
                        //Retrieve parameters at the top level with the name requested
                        IEnumerable<ExperimentParameter> toplevelparams = this.dbcontext.GetExperimentTopLevelParameters(experimentid, false, paramname);

                        //If a parameter does not exist with the name requested, don't graph anything
                        ExperimentParameter firstParam = toplevelparams.FirstOrDefault();
                        if (firstParam == null)
                        {
                            continue;
                        }

                        //Lookup up the parameter path for one of the parameters
                        parampath = firstParam.GetParameterIDPath(this.dbcontext, false) + "\\" + firstParam.Name;
                    }

                    //Edit the entry based on the request (if the parampath has been already been loaded from the database and the series label hasn't been used before)
                    if (this._experimentParameterSelectionList.ContainsKey(parampath) && !definedSeriesParameters.Any(info => seriesGetter(info) == seriesname))
                    {
                        XYParameterGraphExperimentParameterSelection paramInfo = this._experimentParameterSelectionList[parampath];
                        seriesSetter(paramInfo, seriesname);
                        paramInfo.ExperimentID = experimentid;
                        paramInfo.ParentParameterID = parentparamid;
                        paramInfo.DataItemName = paramname;
                    }
                }
            }
        }

        private void ParseGlobalParameterStrings(string[] seriesparameterstrings, XYGraphDataItemSelection.XYGraphDataItemSelectionSeriesSetter seriesSetter, IEnumerable<XYParameterGraphParameterSelection> definedSeriesParameters, XYGraphDataItemSelection.XYGraphDataItemSelectionSeriesGetter seriesGetter)
        {
            foreach (string paramstring in seriesparameterstrings)
            {
                string[] splitString;
                bool parsesuccess = HDCSharedFunctions.TrySplitString(paramstring, ':', 2, out splitString);
                if (parsesuccess)
                {
                    string seriesname = splitString[0];
                    string paramname = splitString[1];

                    string newGraphAxisName = seriesname + paramname;
                    if (!definedSeriesParameters.Any(info => seriesGetter(info) == seriesname))
                    {
                        if (!this._globalParameterSelectionList.ContainsKey(newGraphAxisName))
                        {
                            this._globalParameterSelectionList[newGraphAxisName] = new XYParameterGraphParameterSelection(newGraphAxisName);
                        }
                        XYParameterGraphParameterSelection selectionInfo = this._globalParameterSelectionList[newGraphAxisName];
                        seriesSetter(selectionInfo, seriesname);
                        selectionInfo.DataItemName = paramname;
                    }
                }
            }
        }


        private void RedirectToNewUrl()
        {
            string experimentids = (this._experimentSelectionControl.RequestedExperimentIDs.Count > 0 ? "&" + string.Join("&", this._experimentSelectionControl.RequestedExperimentIDs.Select(id => string.Format("ExperimentID={0}", id)).ToArray()) : "");
            string xseriesparams = (this.XSeriesParameters.Count() > 0 ? "&" + string.Join("&", this.XSeriesParameters.Select(info => string.Format("XSeriesParameter={0}:{1}:{2}:{3}", info.XSeries, info.ExperimentID, info.ParentParameterID,info.DataItemName)).ToArray()) : "");
            string yseriesparams = (this.YSeriesParameters.Count() > 0 ? "&" + string.Join("&", this.YSeriesParameters.Select(info => string.Format("YSeriesParameter={0}:{1}:{2}:{3}", info.YSeries, info.ExperimentID, info.ParentParameterID, info.DataItemName)).ToArray()) : "");
            string xglobalparams = (this.XGlobalParameters.Count() > 0 ? "&" + string.Join("&", this.XGlobalParameters.Select(info => string.Format("XGlobalParameter={0}:{1}", info.XSeries, info.DataItemName)).ToArray()) : "");
            string yglobalparams = (this.YGlobalParameters.Count() > 0 ? "&" + string.Join("&", this.YGlobalParameters.Select(info => string.Format("YGlobalParameter={0}:{1}", info.YSeries, info.DataItemName)).ToArray()) : "");
            string charttitle = "&ChartTitle=" + HttpUtility.UrlEncode(this.txt_ChartTitle.Text);
            string xaxistitle = "&XAxisTitle=" + HttpUtility.UrlEncode(this.txt_XAxisTitle.Text);
            string yaxistitle = "&YAxisTitle=" + HttpUtility.UrlEncode(this.txt_YAxisTitle.Text);
            string redirectUrl = string.Format("{0}?{1}{2}{3}{4}{5}{6}{7}{8}", this.Page.Request.Url.GetLeftPart(UriPartial.Path), experimentids, xseriesparams, yseriesparams, xglobalparams, yglobalparams, charttitle, xaxistitle, yaxistitle);
            this.Response.Redirect(redirectUrl);
        }

        private void CreateGraph()
        {
            //Create graph data in the following format:
            //categories = dataset
            //series = parameter name

            try
            {
                //Set up graph options
                this.SetupGraph();

                //Generate the graph data
                GraphData_XY graphData = this.GetDataForGraph();

                //At this point, we have a graph type, categories, series data and possibly plot options an additional function string. Generate the javascript.
                string js = graphData.FormattedJavaScript;

                //Add script to page
                string ctxkey = "highchart" + this.Page.GetHashCode().ToString("x");
                if (!this.ClientScript.IsClientScriptBlockRegistered(this.GetType(), ctxkey))
                {
                    //We don't want to have the CSS cached so we append the date to the URL
                    this.ClientScript.RegisterClientScriptBlock(this.GetType(), ctxkey, js, false);
                }
            }
            catch (Exception e)
            {
                HDCSharedWebFunctions.AddErrorLabel("Error creating graph: " + e.Message, this.PlaceHolder_Messages);
            }
        }

        private void SetupGraph()
        {
            this.txt_ChartTitle.Text = Page.Request.QueryString["ChartTitle"] ?? "";
            this.txt_XAxisTitle.Text = Page.Request.QueryString["XAxisTitle"] ?? "";
            this.txt_YAxisTitle.Text = Page.Request.QueryString["YAxisTitle"] ?? "";

            //Lookup default values if no values were set
            XYParameterGraphParameterSelection xglobalseries1 = this.XGlobalParameters.FirstOrDefault();
            XYParameterGraphExperimentParameterSelection xexperimentseries1 = this.XSeriesParameters.FirstOrDefault();
            XYParameterGraphParameterSelection yglobalseries1 = this.YGlobalParameters.FirstOrDefault();
            XYParameterGraphExperimentParameterSelection yexperimentseries1 = this.YSeriesParameters.FirstOrDefault();

            //Set X Axis default label
            if (string.IsNullOrEmpty(this.txt_XAxisTitle.Text))
            {
               
                //First global series will take precedence
                if (xglobalseries1 != null)
                {
                    this.txt_XAxisTitle.Text = xglobalseries1.DataItemName;
                }
                //Otherwise use the experiment series
                else if (xexperimentseries1 != null)
                {
                    this.txt_XAxisTitle.Text = xexperimentseries1.DataItemName;
                }
                //If no series have been defined, supply a default value
                else
                {
                    this.txt_XAxisTitle.Text = "X axis";
                }
            }

            //Set Y Axis default label
            if (string.IsNullOrEmpty(this.txt_YAxisTitle.Text))
            {
                //First global series will take precedence
                if (yglobalseries1 != null)
                {
                    this.txt_YAxisTitle.Text = yglobalseries1.DataItemName;
                }
                //Otherwise use the experiment series
                else if (yexperimentseries1 != null)
                {
                    this.txt_YAxisTitle.Text = yexperimentseries1.DataItemName;
                }
                //If no series have been defined, supply a default value
                else
                {
                    this.txt_YAxisTitle.Text = "X axis";
                }
            }

            //Set chart title default
            if (string.IsNullOrEmpty(this.txt_ChartTitle.Text))
            {
                if (xglobalseries1 !=null && yglobalseries1 != null)
                {
                    this.txt_ChartTitle.Text = string.Format("{0} vs {1}", xglobalseries1.DataItemName, yglobalseries1.DataItemName);
                }
                else if (xexperimentseries1 != null && yexperimentseries1 != null)
                {
                    this.txt_ChartTitle.Text = string.Format("{0} vs {1}", xexperimentseries1.DataItemName, yexperimentseries1.DataItemName);
                }
            }
        }

        private GraphData_XY GetDataForGraph()
        {
            Dictionary<string, List<GraphData_XY.XYValues>> graphDataDictionary = new Dictionary<string, List<GraphData_XY.XYValues>>();

            //Read the parameters' data and generate a dictionary that will be used for creating the graph
            Dictionary<string, XYParameterGraphExperimentParameterSelection> xseriesitems = this.XSeriesParameters.ToDictionary(data => data.XSeries);
            Dictionary<string, XYParameterGraphExperimentParameterSelection> yseriesitems = this.YSeriesParameters.ToDictionary(data => data.YSeries);
            foreach (XYParameterGraphExperimentParameterSelection xseries in xseriesitems.Values)
            {
                string xseriesname = xseries.XSeries;

                if (yseriesitems.ContainsKey(xseriesname))
                {
                    XYParameterGraphExperimentParameterSelection yseries = yseriesitems[xseriesname];
                    List<GraphData_XY.XYValues> xydata = this.GetGraphDataForSeriesParam(xseries,yseries);
                    if(xydata == null)
                    {
                        //One of the series did not load so go to next series request
                        continue;
                    }
                    graphDataDictionary.Add(xseriesname, xydata);
                }
            }

            Dictionary<string, XYParameterGraphParameterSelection> xglobalseries = this.XGlobalParameters.ToDictionary(data => data.XSeries);
            Dictionary<string, XYParameterGraphParameterSelection> yglobalseries = this.YGlobalParameters.ToDictionary(data => data.YSeries);
            foreach (XYParameterGraphParameterSelection xitem in xglobalseries.Values)
            {
                string xseriesname = xitem.XSeries;

                if (yglobalseries.ContainsKey(xseriesname))
                {
                    XYParameterGraphParameterSelection yitem = yglobalseries[xseriesname];
                    List<GraphData_XY.XYValues> xydata = this.GetGraphDataForGlobalParam(xitem.DataItemName, yitem.DataItemName);
                    graphDataDictionary.Add(xseriesname, xydata);
                }
            }

            GraphData_XY graphData = new GraphData_XY(graphDataDictionary, "scatter");
            graphData.ChartTitle = this.txt_ChartTitle.Text;
            graphData.XAxisTitle = this.txt_XAxisTitle.Text;
            graphData.YAxisTitle = this.txt_YAxisTitle.Text;
            return graphData;
        }

        private List<GraphData_XY.XYValues> GetGraphDataForSeriesParam(XYParameterGraphExperimentParameterSelection xselectioninfo, XYParameterGraphExperimentParameterSelection yselectioninfo)
        {
            IEnumerable<ExperimentParameter> allXParamsWithName = this.GetParametersMatchingExperimentParameterSelection(xselectioninfo);
            IEnumerable<ExperimentParameter> allYParamsWithName = this.GetParametersMatchingExperimentParameterSelection(yselectioninfo);

            //Check that parameters exist
            if (allXParamsWithName == null || allYParamsWithName == null)
            {
                return null;
            }

            bool sameParentParameter;

            //If xparent != null or yparent!=null then check if xparent==yparent
            if ((xselectioninfo.ParentParameterID > 0) || (yselectioninfo.ParentParameterID > 0))
            {
                sameParentParameter = (xselectioninfo.ParentParameterID == yselectioninfo.ParentParameterID);
            }
            //Otherwise check if xexp == yexp
            else
            {
                sameParentParameter = (xselectioninfo.ExperimentID == yselectioninfo.ExperimentID);
            }

            List<GraphData_XY.XYValues> graphdata = this.FindMatchingYParametersAndGraph(allXParamsWithName, allYParamsWithName, sameParentParameter);
            return graphdata;
        }

        private List<GraphData_XY.XYValues> GetGraphDataForGlobalParam(string xparamname, string yparamname)
        { 
            List<GraphData_XY.XYValues> graphdata = new List<GraphData_XY.XYValues>();

            //Visit each experiment looking for parameters with the requested x parameter
            foreach (int experimentid in this._experimentSelectionControl.RequestedExperimentIDs)
            {
                IEnumerable<ExperimentParameter> allXParamsWithName = this.dbcontext.GetExperimentParameters(experimentid, false, xparamname);
                graphdata.AddRange(this.FindMatchingYParametersAndGraph(allXParamsWithName, yparamname));
            }
            return graphdata;
        }

        private IEnumerable<ExperimentParameter> GetParametersMatchingExperimentParameterSelection(XYParameterGraphExperimentParameterSelection selectionInfo)
        {
            IEnumerable<ExperimentParameter> allParamsWithName = null;
            if (selectionInfo.ParentParameterID > 0)
            {
                ExperimentParameter parentparam = this.dbcontext.GetExperimentParameterByID((int)selectionInfo.ParentParameterID);

                //Check we were passed a sensible parent parameter
                if (parentparam != null)
                {
                    allParamsWithName = parentparam.GetChildParameters(selectionInfo.DataItemName);
                }
                else
                {
                    allParamsWithName = null;
                }
            }

            return allParamsWithName;
        }


        /// <summary>
        /// Iterate around each X parameter and find its parent parameter (or experiment if there is no parent). Then try to find a matching Y parameter using its position.
        /// </summary>
        /// <param name="xParams">A set of X parameters</param>
        /// <param name="yparamname">The name of the Y parameter to find at the same level as the X parameter</param>
        /// <returns>A list of X-Y values for graphing</returns>
        private List<GraphData_XY.XYValues> FindMatchingYParametersAndGraph(IEnumerable<ExperimentParameter> xParams, string yparamname)
        {
            List<GraphData_XY.XYValues> graphdata = new List<GraphData_XY.XYValues>();

            //Dictionary of all X parameters categorised by their parent parameter
            Dictionary<ExperimentParameter, List<ExperimentParameter>> childXParamsByParentParam = new Dictionary<ExperimentParameter, List<ExperimentParameter>>();

            //Dictionary of all X parameters which have no parent parameter categorised by their parent experiment
            Dictionary<Experiment, List<ExperimentParameter>> topLevelXParamsByParentExp = new Dictionary<Experiment, List<ExperimentParameter>>();

            //Iterate through all x parameters, finding whether they are a top level parameter and storing them with other parameters at the same level
            foreach (ExperimentParameter xparam in xParams)
            {
                //Look up the parent parameter
                ExperimentParameter parentParam = this.dbcontext.GetParentParameter(xparam);

                //If there is no parent parameter, it must be a top level parameter so put in the experiment dictionary
                if (parentParam == null)
                {
                    if (!topLevelXParamsByParentExp.ContainsKey(xparam.Experiment))
                    {
                        topLevelXParamsByParentExp.Add(xparam.Experiment, new List<ExperimentParameter>());
                    }
                    List<ExperimentParameter> topLevelXParams = topLevelXParamsByParentExp[xparam.Experiment];
                    topLevelXParams.Add(xparam);
                }
                else
                {
                    if (!childXParamsByParentParam.ContainsKey(parentParam))
                    {
                        childXParamsByParentParam.Add(parentParam, new List<ExperimentParameter>());
                    }
                    List<ExperimentParameter> childXParams = childXParamsByParentParam[parentParam];
                    childXParams.Add(xparam);
                }
            }

            //Now look up the graph data based on all parameters at the same level

            //First look at parameters group by their parent parameter
            foreach (KeyValuePair<ExperimentParameter,List<ExperimentParameter>> childXParamKVP in childXParamsByParentParam)
            {
                //Find all parameters at the same level as the x parameter that can be a candidate for a matching y parameter
                IEnumerable<ExperimentParameter> matchingyparams = childXParamKVP.Key.GetChildParameters(yparamname);
                graphdata.AddRange(this.FindMatchingYParametersAndGraph(childXParamKVP.Value, matchingyparams, true));
            }

            //Then, look at parameters with no parent parameter (grouped by their experiment)
            foreach (KeyValuePair<Experiment, List<ExperimentParameter>> topLevelXParamKVP in topLevelXParamsByParentExp)
            {
                //Find all parameters at the same level as the x parameter that can be a candidate for a matching y parameter
                IEnumerable<ExperimentParameter> matchingyparams = this.dbcontext.GetExperimentTopLevelParameters(topLevelXParamKVP.Key.ID, false, yparamname);
                graphdata.AddRange(this.FindMatchingYParametersAndGraph(topLevelXParamKVP.Value, matchingyparams, true));
            }
            
            return graphdata;
        }


        /// <summary>
        /// Given two lists, of X and Y parameters, try to match each X parameter against a Y parameter, taking into consideration its position
        /// </summary>
        /// <param name="xparams">A set of X parameters</param>
        /// <param name="yparams">A set of Y parameters to match against</param>
        /// <param name="sameParentParameter">Whether the Y parameter has the same parent parameter as the X parameter (which changes the way we look at the position field)</param>
        /// <returns>A list of X-Y values for graphing</returns>
        private List<GraphData_XY.XYValues> FindMatchingYParametersAndGraph(IEnumerable<ExperimentParameter> xparams, IEnumerable<ExperimentParameter> yparams, bool sameParentParameter)
        {
            List<GraphData_XY.XYValues> graphdata = new List<GraphData_XY.XYValues>();

            IEnumerable<ExperimentParameter> xparamswithnullpos = xparams.Where(p => p.Position == null);
            IEnumerable<ExperimentParameter> yparamswithnullpos = yparams.Where(p => p.Position == null);

            //Only permit one parameter with a null position (otherwise we can't guarantee the parameters are matching ones)
            //Only use this method if the count of params with a position match up, then we'll just sort it by position and match them in ascending order
            if (xparamswithnullpos.Count() <= 1 && xparamswithnullpos.Count() == yparamswithnullpos.Count() && xparams.Count() == yparams.Count())
            {
                    ExperimentParameter[] xparamarray = xparams.OrderBy(p => p.Position).ToArray();
                    ExperimentParameter[] yparamarray = yparams.OrderBy(p => p.Position).ToArray();
                    for (int i = 0; i < xparamarray.Count(); i++)
                    {
                        ExperimentParameter xparam = xparamarray[i];
                        ExperimentParameter yparam = yparamarray[i];

                        GraphData_XY.XYValues xydata = new GraphData_XY.XYValues(xparam.Value, yparam.Value);
                        xydata.XExperimentID = xparam.Experiment.ID.ToString();
                        xydata.XParameterID = xparam.ID.ToString();
                        xydata.XPointName = xparam.Name;
                        xydata.YExperimentID = yparam.Experiment.ID.ToString();
                        xydata.YParameterID = yparam.ID.ToString();
                        xydata.YPointName = yparam.Name;
                        graphdata.Add(xydata);
                    }
            }
            //Otherwise, our approach is to try to use the position to find the matching parameter (and ignore ones that can't be found)
            else
            {
                //We cannot do this if there are more than one parameter with a null Position, so we will exclude multiple parameters with a null Position
                if (xparamswithnullpos.Count() > 1)
                {
                    xparams = xparams.Where(p => p.Position != null);
                }

                foreach (ExperimentParameter xparam in xparams)
                {
                    IEnumerable<ExperimentParameter> matchingyparams;

                    //We will try to use the Position field to find a matching Y parameter.
                    //This can be 1 greater than the X if it has the same parent, the same as X if has a different parent, or null if there is only one other matching parameter with null as position.
                    if (xparam.Position != null && sameParentParameter)
                    {
                        //if xparam pos is not null and X and Y have the same parent...yparam pos must be 1 more than xparam 
                        matchingyparams = yparams.Where(yp => yp.ID != xparam.ID && yp.Position == xparam.Position + 1);
                    }
                    else
                    {
                        //if xparam position is null...................................yparam position must be null too
                        //if xparam position is not null with different parent param...yparam position must be the same as xparam 
                        matchingyparams = yparams.Where(yp => yp.ID != xparam.ID && yp.Position == xparam.Position);
                    }

                    //We insist there is only one, otherwise we can't automatically select it so we'll ignore it
                    if (matchingyparams.Count() != 1)
                    {
                        continue;
                    }

                    //We have a match, so add it to the graph
                    ExperimentParameter yparam = matchingyparams.First();
                    GraphData_XY.XYValues xydata = new GraphData_XY.XYValues(xparam.Value, yparam.Value);
                    xydata.XExperimentID = xparam.Experiment.ID.ToString();
                    xydata.XParameterID = xparam.ID.ToString();
                    xydata.XPointName = xparam.Name;
                    xydata.YExperimentID = yparam.Experiment.ID.ToString();
                    xydata.YParameterID = yparam.ID.ToString();
                    xydata.YPointName = yparam.Name;
                    graphdata.Add(xydata);
                }
            }

            return graphdata;
        }
        
        #endregion Private methods


        #region Event handling

        protected void txt_GlobalParam_TextChanged(object sender, EventArgs e)
        {
            try
            {
                TextBox textbox = (TextBox)sender;
                ListViewDataItem parentDataItem = (ListViewDataItem)textbox.NamingContainer;
                System.Web.UI.WebControls.ListView parentListView = (System.Web.UI.WebControls.ListView)(parentDataItem.BindingContainer);

                string seriesname = textbox.Text;
                //string paramnam = 
                //string seriesparampath = parentListView.DataKeys[parentDataItem.DataItemIndex].Value.ToString();
                string seriesselectionkey = parentListView.DataKeys[parentDataItem.DataItemIndex]["SelectionKey"].ToString();
                if (textbox.ID == "txt_XParam")
                {
                    XYParameterGraphParameterSelection param = this._globalParameterSelectionList[seriesselectionkey];
                    param.XSeries = seriesname;
                }
                else
                {
                    XYParameterGraphParameterSelection param = this._globalParameterSelectionList[seriesselectionkey];
                    param.YSeries = seriesname;
                }
                this.redirect = true;
            }
            catch (Exception)
            {
                HDCSharedWebFunctions.AddErrorLabel("Could not parse changed series", this.PlaceHolder_Messages);
            }
        }

        protected void txt_Param_TextChanged(object sender, EventArgs e)
        {
            try
            {
                TextBox textbox = (TextBox)sender;
                ListViewDataItem parentDataItem = (ListViewDataItem)textbox.NamingContainer;
                System.Web.UI.WebControls.ListView parentListView = (System.Web.UI.WebControls.ListView)(parentDataItem.BindingContainer);
                int listViewRowNo = parentDataItem.DataItemIndex;
                string seriesname = textbox.Text;
                string seriesparampath = parentListView.DataKeys[listViewRowNo]["ParameterIDPath"].ToString();
                XYParameterGraphExperimentParameterSelection param = this._experimentParameterSelectionList[seriesparampath];
                if (textbox.ID == "txt_XParam")
                {
                    param.XSeries = seriesname;
                }
                else
                {
                    param.YSeries = seriesname;
                }
                this.redirect = true;
            }
            catch (Exception)
            {
                HDCSharedWebFunctions.AddErrorLabel("Could not parse changed series", this.PlaceHolder_Messages);
            }
        }

        protected void MakeParameterGlobal(object sender, EventArgs e)
        {
            LinkButton linkbutton = (LinkButton)sender;
            ListViewDataItem parentDataItem = (ListViewDataItem)linkbutton.NamingContainer;
            System.Web.UI.WebControls.ListView listview = (System.Web.UI.WebControls.ListView)(parentDataItem.BindingContainer);

            int expid = (int)listview.DataKeys[parentDataItem.DataItemIndex]["ExperimentID"];
            //int parentParamID = (int)listview.DataKeys[parentDataItem.DataItemIndex]["ParentParameterID"];
            string paramname = (string)listview.DataKeys[parentDataItem.DataItemIndex]["DataItemName"];
            string seriesparampath = (string)listview.DataKeys[parentDataItem.DataItemIndex]["ParameterIDPath"];

            XYGraphDataItemSelection.XYGraphDataItemSelectionSeriesSetter seriesSetter;
            XYGraphDataItemSelection.XYGraphDataItemSelectionSeriesGetter seriesGetter;
            IEnumerable<XYParameterGraphParameterSelection> definedSeriesParameters;
            if (linkbutton.ID == "btn_MakeXParameterGlobal")
            {
                seriesSetter = XYGraphDataItemSelection.XSeriesSetter;
                seriesGetter = XYGraphDataItemSelection.XSeriesGetter;
                definedSeriesParameters = this.XGlobalParameters;
            }
            else
            {
                seriesSetter = XYGraphDataItemSelection.YSeriesSetter;
                seriesGetter = XYGraphDataItemSelection.YSeriesGetter;
                definedSeriesParameters = this.YGlobalParameters;
            }

            //Delete from existing series
            if (this._experimentParameterSelectionList.ContainsKey(seriesparampath))
            {
                seriesSetter(this._experimentParameterSelectionList[seriesparampath], String.Empty);
            }

            int seriesno = 0;
            string seriesname;
            string newGraphAxisName;
            do
            {
                seriesname = "Series " + (++seriesno);
                newGraphAxisName = seriesname + paramname;
            }
            while (this._globalParameterSelectionList.ContainsKey(newGraphAxisName) || definedSeriesParameters.Any(info => seriesGetter(info) == seriesname));

            XYParameterGraphParameterSelection selectionInfo = new XYParameterGraphParameterSelection(newGraphAxisName);
            seriesSetter(selectionInfo, seriesname);
            selectionInfo.DataItemName = paramname;
            this._globalParameterSelectionList[newGraphAxisName] = selectionInfo;

            this.redirect = true;
        }

        protected void btn_Refresh_Click(object sender, EventArgs e)
        {

            //Refresh button was clicked. We need to iterate around all items in the global parameters list and see if any series have changed

            System.Web.UI.WebControls.ListView listview = this.ListView_GlobalParameters;
            bool redir = false;

            foreach (ListViewDataItem dataitem in listview.Items)
            {
                string savedparamname = (string)listview.DataKeys[dataitem.DataItemIndex]["DataItemName"];
                string savedkey = (string)listview.DataKeys[dataitem.DataItemIndex]["SelectionKey"];

                TextBox newxseries = dataitem.FindControl("txt_XGlobalParam") as TextBox;
                TextBox newyseries = dataitem.FindControl("txt_YGlobalParam") as TextBox;
                TextBox newparamname = dataitem.FindControl("txt_GlobalParamName") as TextBox;
                XYParameterGraphParameterSelection savedSelectionInfo = this._globalParameterSelectionList[savedkey];

                if (newxseries==null||newyseries==null||newparamname==null||savedSelectionInfo==null)
                {
                    continue;
                }

                //If anything did change, we need to remove the existing entry. This is because the series name is used as the key to finding the parameter selection and the series name might have changed
                if (savedSelectionInfo.XSeries != newxseries.Text || savedSelectionInfo.YSeries != newyseries.Text || savedSelectionInfo.DataItemName != newparamname.Text)
                {
                    this._globalParameterSelectionList.Remove(savedkey);
                    redir = true;
                }

                //Add a new parameter selection if one was made
                if (!string.IsNullOrEmpty(newxseries.Text) && !string.IsNullOrEmpty(newparamname.Text))
                {
                    string xseriesname = newxseries.Text;
                    string newGraphAxisName = xseriesname + newparamname.Text;
                    if (!this.XGlobalParameters.Any(info => XYGraphDataItemSelection.XSeriesGetter(info) == xseriesname))
                    {
                        if (!this._globalParameterSelectionList.ContainsKey(newGraphAxisName))
                        {
                            this._globalParameterSelectionList[newGraphAxisName] = new XYParameterGraphParameterSelection(newGraphAxisName);
                        }
                        XYParameterGraphParameterSelection selectionInfo = this._globalParameterSelectionList[newGraphAxisName];
                        XYGraphDataItemSelection.XSeriesSetter(selectionInfo, newxseries.Text);
                        selectionInfo.DataItemName = newparamname.Text;
                    }

                }
                else if (!string.IsNullOrEmpty(newyseries.Text) && !string.IsNullOrEmpty(newparamname.Text))
                {
                    string yseriesname = newyseries.Text;
                    string newGraphAxisName = yseriesname + newparamname.Text;
                    if (!this.YGlobalParameters.Any(info => XYGraphDataItemSelection.YSeriesGetter(info) == yseriesname))
                    {
                        if (!this._globalParameterSelectionList.ContainsKey(newGraphAxisName))
                        {
                            this._globalParameterSelectionList[newGraphAxisName] = new XYParameterGraphParameterSelection(newGraphAxisName);
                        }
                        XYParameterGraphParameterSelection selectionInfo = this._globalParameterSelectionList[newGraphAxisName];
                        XYGraphDataItemSelection.YSeriesSetter(selectionInfo, yseriesname);
                        selectionInfo.DataItemName = newparamname.Text;
                    }
                }
            }

            this.redirect |= redir;
        }

        protected void ExperimentSelectionChanged(object sender, EventArgs e)
        {
            //This is called when the user makes a selection from the quick search control

            this.redirect = true;
        }

        protected void txt_ChartInfoChanged(object sender, EventArgs e)
        {
            this.redirect = true;
        }

        private void SaveReportQuickSearchControl_ExperimentSelected(object sender, ExperimentSelectedEventArgs e)
        {
            string reportUrl = this.Page.Request.Url.AbsoluteUri;
            int parentExpID = e.SelectedExperimentID;
            Experiment parentExp = this.dbcontext.GetExperimentByID(parentExpID);
            if (parentExp != null)
            {
                //Add a new report to our parameter
                ExperimentParameter savedReportParam = parentExp.AddReportParameter(this.dbcontext, reportUrl);

                if (savedReportParam != null)
                {
                    //Save the changes
                    dbcontext.SaveChanges();

                    //Report what we did to the user
                    HDCSharedWebFunctions.AddMessageHyperLink("Report saved to experiment " + parentExp.ID, parentExp.GetDetailsWebPage(this.dbcontext), this.PlaceHolder_SaveReportToExperimentMessages);
                }
            }
        }

        #endregion Event handling


        #region Data binding

        private void DataBindControls()
        {
            this.PopulateParametersList();
        }

        private void PopulateParametersList()
        {
            //Populate list
            this.ListView_Parameters.DataSource = this._experimentParameterSelectionList.Values;
            this.ListView_Parameters.DataBind();

            this.ListView_GlobalParameters.DataSource = this._globalParameterSelectionList.Values;
            this.ListView_GlobalParameters.DataBind();
        }

        #endregion Data binding

    }
}
